package org.zerta.troops.network;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;

import java.net.Socket;
import java.net.UnknownHostException;

import org.zerta.troops.common.Area;
import org.zerta.troops.common.NetworkMessage;
import org.zerta.troops.common.Strings;
import org.zerta.troops.common.TurnData;


public class TroopsNetworkClient {

	Socket serverSocket;
	PrintWriter output;
	BufferedReader input;
	private String hostname;
	private int port = 0;
	
	public TroopsNetworkClient() {
		
	}
	
	private boolean connect() {
		if (hostname == null) {
			return false;
		}
		
		if (hostname.equals("")) {
			return false;
		}
		
		if (port == 0) {
			return false;
		}
		
		try {
			serverSocket = new Socket(hostname, port);
			output = new PrintWriter(serverSocket.getOutputStream(), true);
			input = new BufferedReader( new InputStreamReader(serverSocket.getInputStream()));
			return true;
		} catch (UnknownHostException e) {
			// Does not know host hostname
			System.err.println("Does not know host hostname");
			return false;
		} catch ( IOException e) {
			// Could not get I/O for the connection to hostname
			System.err.println("Could not get I/O for the connection to hostname");
			return false;
		} 
	}
	
	private boolean disconnect() {
		if (serverSocket == null) {
			System.err.println("Could not close I/O for the connection since the socket is not open");
			return false;
		}
		try {
			output.close();
			input.close();
			serverSocket.close();
			return true;
		} catch ( IOException e) {
			System.err.println("Could not close I/O for the connection");
			e.printStackTrace(System.err);
			return false;
		} 
	}
	
	private NetworkMessage send( NetworkMessage message ) {
		
		if (! connect()) {
			return null;
		}
		
		if (message == null) {
			disconnect();
			return null;
		}
		
		if (serverSocket == null) {
			disconnect();
			return null;
		}
		
		Object inObject;
		ObjectOutputStream outStream;
		ObjectInputStream inStream = null;
		NetworkMessage inObjectMessage = null;
		
		try {
			outStream = new ObjectOutputStream(serverSocket.getOutputStream());
			outStream.flush();
		} catch (IOException e) {
			System.err.println("Failed to create outStream");
			e.printStackTrace(System.err);
			return null;
		}

		if (message.isSynchronized()) {
			try {
				inStream = new ObjectInputStream(serverSocket.getInputStream());			
			} catch (IOException e) {
				System.err.println("Failed to create inStream");
				e.printStackTrace(System.err);
				disconnect();
				return null;
			}
		}
		
		try {
			outStream.writeObject(message);
			outStream.flush();
		} catch (IOException e) {
			System.err.println("Failed to writeObject because of IOException");
			e.printStackTrace(System.err);
			disconnect();
			return null;
		}

		if (message.isSynchronized()) {
			try {
				inObject = inStream.readObject();			
			} catch (ClassNotFoundException e) {
				System.err.println("Failed to readObject because of ClassNotFoundException");
				e.printStackTrace(System.err);
				disconnect();
				return null;
			} catch (IOException e) {
				System.err.println("Failed to readObject because of IOException");
				e.printStackTrace(System.err);
				disconnect();
				return null;
			}
			
			if ( inObject instanceof NetworkMessage ) {
				inObjectMessage = (NetworkMessage) inObject;
			}
			disconnect();
			return inObjectMessage;
		} else {
			disconnect();
			return null;
		}
		
	}
	
	public void joinGame( String playerName ) {
		NetworkMessage request = new NetworkMessage(Strings.Network.JOIN_GAME_AS_PLAYER, playerName, true);
		NetworkMessage response = send( request );
		if (response != null && response.getId().equals(Strings.Network.JOIN_GAME_AS_PLAYER_ACK)) {
			Area givenArea = (Area) response.getObject();
			System.out.println(givenArea.getName());
		} else {
			System.out.println("Could not add player");
		}
	}
	
	public void sendTurnData(TurnData turnData) {
		NetworkMessage request = new NetworkMessage(Strings.Network.SEND_TURN_DATA, turnData, false);
		send( request );
	}

	public void setHostname(String hostname) {
		this.hostname = hostname;
	}

	public String getHostname() {
		return hostname;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public int getPort() {
		return port;
	}

}
